// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: ziyan

#include <cxxopts.hpp>

#include "coin/proto/coin_order_gateway.pb.h"
#include "coin2/app/driver.h"
#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/base/log.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/api_util/json_writer.h"
#include "coin2/strategy/strategy.h"

using namespace coin2::exchange::base::symbology;
using namespace coin2::base::config;
using namespace coin2::exchange::feed;
using namespace coin2::app;
using coin2::exchange::base::feed::FeedUpdate;
// using coin2::exchange::base::order::OrderUpdate;

class Supervisor : public ::coin2::strategy::IStrategy {
 public:
  explicit Supervisor(const std::string& outputPath) : outputPath_(outputPath){}

  void onTradeFeed(const FeedUpdate& upd) override { 
    tradeMissingSymbol.erase(upd.product().relative_norm());
    StopChecking();
  }

  void onBookFeed(const FeedUpdate& upd) override { 
    bookMissingSymbol.erase(upd.product().relative_norm());
    StopChecking(); 
  }

  void Init(Driver* driver) override {
    auto config = const_cast<DriverConfig&>(driver->config());
    auto ci = &(driver->GetConstants());
    this->machine = *(config.mutable_feed()->mutable_common()->mutable_archive()->mutable_machine());
    this->worker = config.mutable_feed()->mutable_common()->mutable_archive()->worker();
    auto exchangesMap = config.mutable_feed()->mutable_exchanges();
    for (auto& [mea, configs] : *exchangesMap) {
      if (this->mea == "") {
        this->mea = mea;
        auto mea_struct = MarketExchangeApi::FromString(mea);
        if (configs.mutable_products()->groups_size() != 0) {
          auto groups = configs.mutable_products()->mutable_groups();
          for (auto& group : *groups) {
            auto norms = ci->GetSymbolGroups().GetRelativeNormSymbols(mea_struct, group[0]);
            tradeMissingSymbol.insert(norms.begin(), norms.end());
            bookMissingSymbol.insert(norms.begin(), norms.end());
          }
        } else if (configs.mutable_products()->norms_size() != 0) {
          auto norms = configs.mutable_products()->mutable_norms();
          tradeMissingSymbol.insert(norms->begin(), norms->end());
          bookMissingSymbol.insert(norms->begin(), norms->end());
        } else {
          throw std::runtime_error(std::string("you must specific groups or norms to archive"));
        }
      }
    }
  }

  void WriteToJson() {
    std::string path = this->outputPath_ + "/" + this->mea + ".json";
    std::fstream outfile;
    rapidjson::Document doc;
    doc.SetObject();
    rapidjson::Document::AllocatorType& allocator = doc.GetAllocator();

    rapidjson::Value meaValue,machineValue,workerValue;
    meaValue.SetString(this->mea.c_str(), allocator);
    machineValue.SetString(this->machine.c_str(),allocator);
    workerValue.SetInt(this->worker);
    doc.AddMember("machine", machineValue,allocator);
    doc.AddMember("mea", meaValue,allocator);
    doc.AddMember("worker", workerValue,allocator);

    if(!tradeMissingSymbol.empty()){
      rapidjson::Value tmissingSymbolValue;
      tmissingSymbolValue.SetArray();
      for (auto& symbol : tradeMissingSymbol){
        rapidjson::Value symbolValue;
        symbolValue.SetString(symbol.c_str(),allocator);
        tmissingSymbolValue.PushBack(symbolValue,allocator);
      }
      doc.AddMember("trade_missing_symbol", tmissingSymbolValue,allocator);
    }
    
    if(!bookMissingSymbol.empty()){
      rapidjson::Value bmissingSymbolValue;
      bmissingSymbolValue.SetArray();
      for (auto& symbol : bookMissingSymbol){
        rapidjson::Value symbolValue;
        symbolValue.SetString(symbol.c_str(),allocator);
        bmissingSymbolValue.PushBack(symbolValue,allocator);
      }
      doc.AddMember("book_missing_symbol", bmissingSymbolValue,allocator);
    }

    rapidjson::StringBuffer strBuf;
    rapidjson::Writer<rapidjson::StringBuffer> writer(strBuf);
    doc.Accept(writer);

    std::string jsonstr = strBuf.GetString();
    outfile.open(path, std::ios::out);
    outfile << jsonstr << std::endl;
    LOG(INFO) << jsonstr << std::endl;
    outfile.close();
  }

 private:
  std::unordered_set<std::string> tradeMissingSymbol;
  std::unordered_set<std::string> bookMissingSymbol;
  std::string mea = "";
  std::string machine = "";
  int worker = -1;
  std::string outputPath_;

  void StopChecking() {
    if (tradeMissingSymbol.empty() && bookMissingSymbol.empty()) {
      LOG(INFO) << "all symbols are good";
      exit(0);
    }
  }

};

int main(int argc, char* argv[]) {
  google::InitGoogleLogging(argv[0]);

  DriverConfig app;

  cxxopts::Options opt("Supervisor", "Check symbol health status.");
  opt.add_options()("output_file_path", "", cxxopts::value<std::string>());
  AddDriverOptions(&opt);
  AddIntervalOptions(&opt);
  AddProductOptions(&opt);

  auto res = opt.parse(argc, argv);
  ParseDriverOptions(res, &app);
  ParseIntervalOptions(
      res,
      app.mutable_feed()->mutable_common()->mutable_archive()->mutable_interval());
  ParseProductOptions(res, app.mutable_feed());

  LOG(INFO) << app.DebugString();

  Supervisor supervisor(res["output_file_path"].as<std::string>());
  Driver driver(app, &supervisor);
  driver.Run();
  LOG(INFO) << "have missing symbol";
  supervisor.WriteToJson();

  return 0;
}
